﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TimeWar.Shared.Animation;
using TimeWar.Engine.Primitives;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace TimeWar.Engine.SceneGraph
{
    public enum SceneNodeType
    {
        StaticModel,
        AnimatedModel,
        AnimatedCharacter,
    }

    public interface IEffectContext
    {
        void SetPosOri(Matrix posOri);
        void SetColorParameters(Vector3 diffuse, Vector3 specular, Vector3 emissive, float power);
        void SetTextureNormalmap(Texture texture, Texture normalmap);
        void Begin();
        void End();
    }

    public struct DrawMesh
    {
        public ModelMesh mesh_;
        public IEffectContext effect_;
        public ModelMeshPart part_;
    }


    public class SceneNode
    {

        #region Attributes

        #region All 3D Objects
        protected SceneNodeType m_type;
        protected Camera camera;
        protected SkinnedSphere[] skinnedSpheres;
        protected BoundingSphere[] boundingSpheres;
        protected bool showSpheres;
        protected SpherePrimitive spherePrimitive;
        protected Model currentModel;
        protected Int32 objectId = 0;
        protected Vector3 position;
        protected Quaternion rotation = Quaternion.Identity;
        protected float scaleX;
        protected float scaleY;
        protected float scaleZ;
        protected Vector3 up;
        protected Vector3 direction;
        protected Vector3 right;
        protected Matrix world;
        protected const float rotationRate = 1.5f;
        protected DrawMesh[] drawMeshes_;
        protected GraphicsDevice device;
        protected Vector3 lightPosition;

        #endregion All 3D Objects
        

        #region Animated 3D Objects
        protected AnimationPlayer animationPlayer;
        protected SkinningData skinningData;
        protected Matrix[] boneTransforms;
        protected Model baseballBat;

        #endregion Animated 3D Objects


        #region Animated Character
        protected Matrix headTransform;
        protected Matrix armTransform;

        #endregion Animated Character


        #endregion Attributes

        public Quaternion Rotation
        {
            get { return rotation; }
            set { rotation = value; }
        }

        public float RotationX
        {
            get { return rotation.X; }
            set { rotation.X = value; }
        }

        public float RotationY
        {
            get { return rotation.Y; }
            set { rotation.Y = value; }
        }

        public float RotationZ
        {
            get { return rotation.Z; }
            set { rotation.Z = value; }
        }

        public Vector3 Direction
        {
            get
            {
                direction.Normalize();
                return direction;
            }
            set { direction = value; }
        }

        public Vector3 Up
        {
            get { return up; }
            set { up = value; }
        }

        public Vector3 Right
        {
            get { return right; }
        }

        public float Scale
        {
            set
            {
                scaleX = value;
                scaleY = value;
                scaleZ = value;
            }
        }

        public Vector3 Position
        {
            get { return position; }
            set { position = value; }
        }

        public Vector3 Light
        {
            get { return lightPosition; }
            set { lightPosition = value; }
        }


        public SceneNode(SceneNodeType nodeType, GraphicsDevice dev)
        {
            m_type = nodeType;
            position = new Vector3(0, 0, 0);
            scaleX = 1.0f;
            scaleY = 1.0f;
            scaleZ = 1.0f;
            up = Vector3.Up;
            direction = new Vector3(0, 0, 1f);
            right = Vector3.Right;
            device = dev;
        }

        public void LoadContent(Model tempModel, Effect effect)
        {

            //Based on http://forums.xna.com/forums/p/37408/217064.aspx#217064
            if (null == tempModel || null == effect)
            {
                return;
            }

            currentModel = tempModel;
            effect.Parameters["LightDistanceSquared"].SetValue(200f);
            effect.Parameters["LightDiffuseColor"].SetValue(new Vector3(0.3f, 0.3f, 0.3f));

            foreach (ModelMesh mesh in tempModel.Meshes)  
            {  
                BasicEffect basicEffect;  
                Effect effectCopy;  
  
                foreach (ModelMeshPart meshpart in mesh.MeshParts)  
                {
                    effectCopy = effect.Clone(effect.GraphicsDevice);  
                    basicEffect = new BasicEffect(device, meshpart.Effect.EffectPool);
  
                    effectCopy.Parameters["DiffuseColor"].SetValue(basicEffect.DiffuseColor);  
                    effectCopy.Parameters["EmissiveColor"].SetValue(basicEffect.EmissiveColor);  
                    effectCopy.Parameters["AmbientLightColor"].SetValue(basicEffect.AmbientLightColor);  
                    effectCopy.Parameters["SpecularColor"].SetValue(basicEffect.SpecularColor);

                    if (m_type == SceneNodeType.AnimatedCharacter || m_type == SceneNodeType.AnimatedModel)
                    {
                        effectCopy.CurrentTechnique = effect.Techniques["SkinnedModelTechnique"];
                    }
                    else
                    {
                        effectCopy.CurrentTechnique = effect.Techniques["TechniqueWithTexture"];
                    }

                    if (basicEffect.TextureEnabled)
                    {
                        effectCopy.Parameters["DiffuseTexture"].SetValue(basicEffect.Texture);
                        effectCopy.Parameters["DiffuseColor"].SetValue(new Vector3(1, 1, 1));
                    }

                    //if (basicEffect.TextureEnabled)  
                    //{  
                    //    effectCopy.CurrentTechnique = effect.Techniques["TechniqueWithTexture"];  
                    //    effectCopy.Parameters["DiffuseTexture"].SetValue(basicEffect.Texture);  
                    //    effectCopy.Parameters["DiffuseColor"].SetValue(new Vector3(1, 1, 1));  
                    //}  
                    //else  
                    //{  
                    //    effectCopy.CurrentTechnique = effect.Techniques["TechniqueWithoutTexture"];  
                    //}
  
                    meshpart.Effect = effectCopy;  
                }  
            }  

        }


        public void UpdateCamera(Camera currCamera)
        {
            camera = currCamera;
        }

        public void setAnimationPlayer(String clipName)
        {
            if (String.IsNullOrEmpty(clipName))
            {
                return;
            }

            // Look up our custom skinning information.
            skinningData = currentModel.Tag as SkinningData;

            if (skinningData == null)
                throw new InvalidOperationException
                    ("This model does not contain a SkinningData tag.");

            boneTransforms = new Matrix[skinningData.BindPose.Count];

            // Create an animation player, and start decoding an animation clip.
            animationPlayer = new AnimationPlayer(skinningData);

            AnimationClip clip = skinningData.AnimationClips[clipName];

            animationPlayer.StartClip(clip);

        }

        /// <summary>
        /// Rotate Object towards Target Quaternion
        /// </summary>
        /// <param name="slerpTarget">Target Quaternion</param>
        /// <param name="factor">Percentage of Rotation</param>
        public virtual void Slerp(Quaternion slerpTarget, float factor)
        { rotation = Quaternion.Slerp(rotation, slerpTarget, factor); }

        /// <summary>
        /// Rotate Object towards Target Vector
        /// </summary>
        /// <param name="target">Target Vector</param>
        /// <param name="factor">Percentage of Rotation</param>
        public virtual void LookAt(Vector3 target, float factor)
        {
            Vector3 tminusp = target - position;
            Vector3 ominusp = Vector3.Forward;

            tminusp.Normalize();

            float theta = (float)System.Math.Acos(Vector3.Dot(tminusp, ominusp));
            Vector3 cross = Vector3.Cross(ominusp, tminusp);
            string x = cross.X.ToString();
            string y = cross.Y.ToString();
            string z = cross.Z.ToString();

            cross.Normalize();
            if (cross.X.ToString().Contains("NaN")) cross.X = 0.0f;
            if (cross.Y.ToString().Contains("NaN")) cross.Y = 0.0f;
            if (cross.Z.ToString().Contains("NaN")) cross.Z = 0.0f;

            Quaternion targetQ = Quaternion.CreateFromAxisAngle(cross, theta);
            Slerp(targetQ, factor);
        }



        public void setSpherePrimitive(SpherePrimitive primitive)
        {
            spherePrimitive = primitive;
        }

        public void Update(GameTime gameTime)
        {
            float elapsed = 1f;

            // Determine rotation amount from input
            Vector2 rotationAmount = new Vector2(RotationX, RotationY);

            // Scale rotation amount to radians per second
            rotationAmount = rotationAmount * rotationRate * elapsed;

            // Correct the X axis steering when the ship is upside down
            if (up.Y < 0)
                rotationAmount.X = -rotationAmount.X;


            // Create rotation matrix from rotation amount
            Matrix rotationMatrix = Matrix.CreateFromAxisAngle(Right, rotationAmount.Y) * Matrix.CreateRotationY(rotationAmount.X);
            //Matrix rotationMatrix = Matrix.CreateFromQuaternion(objRotation);
            rotation = Quaternion.CreateFromRotationMatrix(rotationMatrix);

            direction = Vector3.Transform(Direction, rotation);
            up = Vector3.Transform(up, rotation);
            right = Vector3.Transform(right, rotation);

            direction.Normalize();
            up.Normalize();
            right.Normalize();

            // Reconstruct the ship's world matrix
            world = Matrix.Identity;
            world.Forward = Direction;
            world.Up = up;
            world.Right = right;
            world.Translation = position;

            if (m_type == SceneNodeType.AnimatedCharacter)
            {
                // Tell the animation player to compute the latest bone transform matrices.
                animationPlayer.UpdateBoneTransforms(gameTime.ElapsedGameTime, true);

                // Copy the transforms into our own array, so we can safely modify the values.
                animationPlayer.GetBoneTransforms().CopyTo(boneTransforms, 0);

                // Modify the transform matrices for the head and upper left arm bones.
                int headIndex = skinningData.BoneIndices["Head"];
                int armIndex = skinningData.BoneIndices["L_UpperArm"];

                boneTransforms[headIndex] = headTransform * boneTransforms[headIndex];
                boneTransforms[armIndex] = armTransform * boneTransforms[armIndex];

                // Tell the animation player to recompute the world and skin matrices.
                animationPlayer.UpdateWorldTransforms(Matrix.Identity, boneTransforms);
                animationPlayer.UpdateSkinTransforms();

                UpdateBoundingSpheres();

            }


            

        }

        public void Draw(GameTime gameTime)
        {
            if (null == camera)
            {
                return;
            }

            Matrix[] bones;

            Matrix worldMatrix = Matrix.CreateScale(scaleX, scaleY, scaleZ)
                * Matrix.CreateRotationY(MathHelper.Pi)
                * Matrix.CreateFromQuaternion(rotation)
                * Matrix.CreateTranslation(position);

            if (m_type == SceneNodeType.AnimatedCharacter || m_type == SceneNodeType.AnimatedModel)
            {
                bones = animationPlayer.GetSkinTransforms();
                //currentModel.CopyAbsoluteBoneTransformsTo();
            }
            else
            {
                //replace with static model code!
                bones = new Matrix[1];
            }


            #region Original animation rendering

             //Render the skinned mesh.
            foreach (ModelMesh mesh in currentModel.Meshes)
            {
                foreach (Effect effect in mesh.Effects)
                {
                    if (m_type == SceneNodeType.AnimatedCharacter || m_type == SceneNodeType.AnimatedModel)
                    {
                        effect.Parameters["Bones"].SetValue(bones);
                    }
                    effect.Parameters["View"].SetValue(camera.View);
                    effect.Parameters["Projection"].SetValue(camera.Projection);
                }

                try
                {
                    mesh.Draw();
                }
                catch (Exception ex)
                {
                    String err = ex.ToString();
                }
            }

            #endregion Original animation rendering

            #region First Multiple model draw attempt

            //if (null != currentModel)
            //{

            //    foreach (ModelMesh mesh in currentModel.Meshes)
            //    {
            //        drawMeshes_ = mesh.MeshParts;

            //        Int32 n = drawMeshes_.Length;

            //        for (Int32 i = 0; i < n; i++)
            //        {
            //            DrawMesh dm = drawMeshes_[i];

            //            if (m_type == SceneNodeType.AnimatedCharacter || m_type == SceneNodeType.AnimatedModel)
            //            {
            //                //dm.mesh_.ParentBone.Transform = bones[dm.mesh_.ParentBone.Index];
            //                //dm.mesh_.ParentBone.Transform = bones[dm.mesh_.ParentBone.Index] * worldMatrix;
            //                dm.effect_.SetPosOri(bones[dm.mesh_.ParentBone.Index] * worldMatrix);

            //            }

            //            dm.effect_.Begin();
            //            device.Vertices[0].SetSource(dm.mesh_.VertexBuffer, dm.part_.StreamOffset, dm.part_.VertexStride);
            //            device.Indices = dm.mesh_.IndexBuffer;
            //            device.VertexDeclaration = dm.part_.VertexDeclaration;
            //            device.DrawIndexedPrimitives(PrimitiveType.TriangleList,
            //                dm.part_.BaseVertex, 0, dm.part_.NumVertices,
            //                dm.part_.StartIndex, dm.part_.PrimitiveCount);
            //            dm.effect_.End();

            //        }

            //    }


            //}

            #endregion First Multiple model draw attempt


            #region Draw supporting multiple models

            //based on: http://forums.xna.com/forums/p/37408/217064.aspx#217064

            //foreach (ModelMesh mesh in currentModel.Meshes)
            //{
            //    foreach (Effect effect in mesh.Effects)
            //    {
            //        if (m_type == SceneNodeType.AnimatedModel || m_type == SceneNodeType.AnimatedCharacter)
            //        {
            //            effect.Parameters["Bones"].SetValue(bones);
            //        }
            //        effect.Parameters["World"].SetValue(worldMatrix);
            //        effect.Parameters["View"].SetValue(camera.View);
            //        effect.Parameters["Projection"].SetValue(camera.Projection);
            //        effect.Parameters["LightPosition"].SetValue(lightPosition);
            //        effect.Parameters["CameraPos"].SetValue(camera.Position);

            //    }
            //    mesh.Draw();
            //}
            
            #endregion Draw supporting multiple models

        }

        /// <summary>
        /// Draws the baseball bat.
        /// </summary>
        public void DrawBaseballBat(Matrix view, Matrix projection)
        {
            int handIndex = skinningData.BoneIndices["L_Index1"];

            Matrix[] worldTransforms = animationPlayer.GetWorldTransforms();

            // Nudge the bat over so it appears between the left thumb and index finger.
            Matrix batWorldTransform = Matrix.CreateTranslation(-1.3f, 2.1f, 0.1f) *
                                       worldTransforms[handIndex];

            foreach (ModelMesh mesh in baseballBat.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.World = batWorldTransform;
                    effect.View = view;
                    effect.Projection = projection;

                    effect.EnableDefaultLighting();
                }

                mesh.Draw();
            }
        }

        /// <summary>
        /// Draws the animated bounding spheres.
        /// </summary>
        public void DrawBoundingSpheres(Matrix view, Matrix projection)
        {

            foreach (BoundingSphere sphere in boundingSpheres)
            {
                Matrix world = Matrix.CreateScale(sphere.Radius) *
                               Matrix.CreateTranslation(sphere.Center);

                spherePrimitive.Draw(world, view, projection, Color.White);
            }


        }




        /// <summary>
        /// Updates the boundingSpheres array to match the current animation state.
        /// </summary>
        protected void UpdateBoundingSpheres()
        {
            // Look up the current world space bone positions.
            Matrix[] worldTransforms = animationPlayer.GetWorldTransforms();

            for (int i = 0; i < skinnedSpheres.Length; i++)
            {
                // Convert the SkinnedSphere description to a BoundingSphere.
                SkinnedSphere source = skinnedSpheres[i];
                Vector3 center = new Vector3(source.Offset, 0, 0);
                BoundingSphere sphere = new BoundingSphere(center, source.Radius);

                // Transform the BoundingSphere by its parent bone matrix,
                // and store the result into the boundingSpheres array.
                int boneIndex = skinningData.BoneIndices[source.BoneName];

                boundingSpheres[i] = sphere.Transform(worldTransforms[boneIndex]);
            }
        }



        public void setHeadRotation(float headRotation)
        {
            headTransform = Matrix.CreateRotationX(headRotation);
        }

        public void setArmRotation(float armRotation)
        {
            armTransform = Matrix.CreateRotationY(-armRotation);
        }




        public Model CurrentModel
        {
            set { currentModel = value; }
        }

        public Matrix[] BoneTransforms
        {
            set { boneTransforms = value; }
        }

        public SkinnedSphere[] SkinnedSpheres
        {
            set
            {
                skinnedSpheres = value;
                boundingSpheres = new BoundingSphere[skinnedSpheres.Length];
            }
        }

        public Boolean ShowSpheres
        {
            get { return showSpheres; }
            set { showSpheres = value; }
        }

        public Model BaseballBat
        {
            set { baseballBat = value; }
        }
        

    }
}
